package zh;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import robocode.*;
import zh.logging.MyLogger;

/**
 * In charge of determining our location and movement strategy in relation to the other opponent.
 * 
 * 
 * TODO
 * 1. Don't get backed into a corner
 * 2. Wall avoidance
 * 3. Position
 * 
 * @author zheismann
 */
public class Navigator
{
    private static MyLogger logger = MyLogger.getLogger(Navigator.class.getName());
    static { logger.setEnabled(true); logger.setLevel(Level.FINER);}
    private UnderDog robot;
    public boolean startGameProceduresExecuted;
    private List<Point2D> WAYPOINTLIST;
    private Point2D currentWayPoint;
    private MoveCompleteCondition moveCompleteCondition;
    private boolean NORMAL_DIRECTION = true;
    
    private double WIDTH;
    private double HEIGHT;
    private double ROBOT_WIDTH_PADDING;
    private double ROBOT_HEIGHT_PADDING;
    private Rectangle2D northBufferZone;
    private Rectangle2D eastBufferZone;
    private Rectangle2D southBufferZone;
    private Rectangle2D westBufferZone;
    private Point2D nextLocation;

    private Random randomVelocity = new Random();
    private double lastDistanceSegmenterValue = 1.0;
    private double currDistanceSegmenterValue = 2.0;
    
    public Navigator(UnderDog r)
    {
        robot = r;
    }
    
    public void makeDecision()
    {
        /**/
        if (!startGameProceduresExecuted && robot.getOthers() > 0)
        {
            moveCompleteCondition = new MoveCompleteCondition(robot);
            startGameProceduresExecuted = true;
//            robot.addCustomEvent(moveCompleteCondition);
            WIDTH = robot.getBattleFieldWidth();
            HEIGHT = robot.getBattleFieldHeight();
            ROBOT_WIDTH_PADDING = robot.getWidth() * 1.25D;
            ROBOT_HEIGHT_PADDING = robot.getHeight() * 1.25D;
            northBufferZone = new Rectangle2D.Double(0, HEIGHT - ROBOT_HEIGHT_PADDING, WIDTH, ROBOT_HEIGHT_PADDING);
            eastBufferZone = new Rectangle2D.Double(WIDTH - ROBOT_WIDTH_PADDING, 0, ROBOT_WIDTH_PADDING, HEIGHT);
            southBufferZone = new Rectangle2D.Double(0, 0, WIDTH, ROBOT_HEIGHT_PADDING);
            westBufferZone = new Rectangle2D.Double(0, 0, ROBOT_WIDTH_PADDING, HEIGHT);

            
            List<Point2D> temp = new ArrayList<Point2D>();
            temp.add(new Point2D.Double((WIDTH/3)*1, HEIGHT - ROBOT_HEIGHT_PADDING));
            temp.add(new Point2D.Double(WIDTH/2.0, HEIGHT - (ROBOT_HEIGHT_PADDING * 2)));
            temp.add(new Point2D.Double((WIDTH/3)*2, HEIGHT - (ROBOT_HEIGHT_PADDING * 1.5)));
            temp.add(new Point2D.Double(WIDTH - ROBOT_WIDTH_PADDING, HEIGHT/2.0));
            temp.add(new Point2D.Double((WIDTH/3)*2, (ROBOT_HEIGHT_PADDING * 2)));
            temp.add(new Point2D.Double(WIDTH/2.0, ROBOT_HEIGHT_PADDING));
            temp.add(new Point2D.Double((WIDTH/3)*1, (ROBOT_HEIGHT_PADDING * 1.5)));
            temp.add(new Point2D.Double(ROBOT_WIDTH_PADDING, HEIGHT/2.0));
            
            WAYPOINTLIST = Collections.unmodifiableList(temp);
nextLocation = temp.get(0);          
currentWayPoint = getNextWayPoint();
             /*
            Point2D p = getClosestCornerPoint();
            determineHeadingChangeToGoToPoint(p);
             */
        }
        /*
        else
        {/* * /
            // Am I moving?  I should be, standing still means death.
            if (!processedFirstMCC) //robot.getDistanceRemaining() == 0.0)
            {
                if (currentWayPoint == null)
                {
                    currentWayPoint = getClosestWayPoint();
                }
                else
                {
                    currentWayPoint = getNextWayPoint();
                }
                determineHeadingChangeToGoToPoint(currentWayPoint);
            }/* * /
        }
    */
    }
    
    public void processEvent(CustomEvent event)
    {
        /*
        if (event.getCondition() instanceof MoveCompleteCondition)
        {
            if (currentWayPoint == null)
            {
                currentWayPoint = getClosestWayPoint();
            }
            else
            {
                currentWayPoint = getNextWayPoint();
            }
            determineHeadingChangeToGoToPoint(currentWayPoint);
        }
         **/
    }
    
    private Point2D getClosestCornerPoint()
    {
        Point2D p = new Point2D.Double(((robot.getX() > (WIDTH / 2.0D)) ? WIDTH : 0.0),((robot.getY() > (HEIGHT / 2.0D)) ? HEIGHT : 0.0));
        // change this value a bit to avoid hitting the wall
        p.setLocation(Math.abs(p.getX() - ROBOT_WIDTH_PADDING), Math.abs(p.getY() - ROBOT_HEIGHT_PADDING));
        return p;
    }
    
    private Point2D getClosestWayPoint()
    {
        Point2D nextWayPoint = null;
        for (Point2D wayPoint : WAYPOINTLIST)
        {
            if (nextWayPoint == null || robot.getLocation().distance(wayPoint) < robot.getLocation().distance(nextWayPoint))
            {
                nextWayPoint = wayPoint;
            }
        }
        return nextWayPoint;
    }
    
    private Point2D getNextWayPoint()
    {
        List<Point2D> TMP = new ArrayList<Point2D>(WAYPOINTLIST);
        if (!NORMAL_DIRECTION)
        {
            Collections.reverse(TMP);
        }
        Point2D nextWayPoint = null;
        if (currentWayPoint == null || TMP.get(TMP.size()-1).equals(currentWayPoint))
        {
            nextWayPoint = TMP.get(0);
        }
        else
        {
            boolean foundCurrent = false;
            for (Point2D wayPoint : TMP)
            {
                if (wayPoint.equals(currentWayPoint))
                {
                    foundCurrent = true;
                }
                else if (foundCurrent)
                {
                    nextWayPoint = wayPoint;
                    break;
                }
            }
        }
        
        return nextWayPoint;
    }
    
    private void determineHeadingChangeToGoToPoint(Point2D destination)
    {
        determineHeadingChangeToGoToPoint(destination, true);
    }
    
    /**
     * @return distance to destination (this will either be a positive or negative value based upon
     * which direction the robot is turning)
     */
    private double determineHeadingChangeToGoToPoint(Point2D destination, boolean goToLocation)
    {
        double distance = getDistanceToPointFromCurrent(destination);
        double angle = normalRelativeAngle(absoluteBearing(destination) - robot.getHeading());
        // if more than a 90 degree turn, then it will be quicker to turn the opposite direction
        // and go backwards, thus the: "distance *= -1;"
        if (Math.abs(angle) > 90) 
        {
            distance *= -1;
            if (angle > 0) 
            {
                angle -= 180;
            }
            else 
            {
                angle += 180;
            }
        }
        robot.setTurnRight(angle);
        if (goToLocation)
        {
            robot.setAhead(distance);
            //setRobotAheadMovement(distance, getNextSegmenterValue());
        }
        logger.log(Level.FINE, "setTurnRight("+angle+")");
        logger.log(Level.FINE, "setAhead("+distance+")");

        return distance;
    }
    
    private void setRobotAheadMovement(double distance, double modByValue)
    {
        double remainder = (distance % modByValue);
        double modifiedDistance = (distance - remainder);
        boolean remainderUsed = false;
        double tempDist = 0.0;
        for (double i = 0.0; i < modByValue; i++)
        {
            if (!remainderUsed)
            {
                remainderUsed = true;
                tempDist += (modifiedDistance/modByValue)+remainder;
                robot.setAhead(tempDist);
            }
            else
            {
                tempDist += (modifiedDistance/modByValue);
                robot.setAhead(modifiedDistance/modByValue);
            }
        }
    }
    
    
    private double getNextSegmenterValue()
    {
        if (lastDistanceSegmenterValue < currDistanceSegmenterValue) 
        {
            if (currDistanceSegmenterValue < 4.0)
            {
                lastDistanceSegmenterValue = currDistanceSegmenterValue;
                currDistanceSegmenterValue++;
            }
            else
            {
                lastDistanceSegmenterValue = currDistanceSegmenterValue;
                currDistanceSegmenterValue--;
            }
        }
        else if (lastDistanceSegmenterValue > currDistanceSegmenterValue)
        {
            if (currDistanceSegmenterValue > 1.0)
            {
                lastDistanceSegmenterValue = currDistanceSegmenterValue;
                currDistanceSegmenterValue--;
            }
            else if (currDistanceSegmenterValue <= 1.0)
            {
                lastDistanceSegmenterValue = 0.0;
            }            
        }
        return currDistanceSegmenterValue;
    }
    
    private double getNextRandomVelocity()
    {
        return 50.0 * randomVelocity.nextDouble();
    }
    
    private double absoluteBearing(Point2D target)
    {
        double dx = target.getX() - robot.getX();
        double dy = target.getY() - robot.getY();
        // Calculate angle to target
        // converts the rectangular coordinates to polar.
        double theta = Math.toDegrees(Math.atan2(dx, dy));
        return theta;
    }
    private double normalRelativeAngle(double angle)
    {
        angle = Math.toRadians(angle);
        double returnvalue = Math.toDegrees(Math.atan2(Math.sin(angle), Math.cos(angle)));
        // polar to rectangular conversion   x = r cosANGLE  y = r sinANGLE    
        // rect to polar r^2 = x^2 + y^2   tanANGLE = y/x  http://www.mathwords.com/p/polar_rectangular_conversion_formulas.htm
        return returnvalue;
    }
    
    private double getDistanceToPointFromCurrent(Point2D otherPoint)
    {
        return Point2D.distance(robot.getX(), robot.getY(), otherPoint.getX(), otherPoint.getY());
    }
    
    double lastKnownEnergy = 0.0;
    double FORWARD = 1.0;
    public void processEvent(ScannedRobotEvent event)
    {/*
        if (lastKnownEnergy > event.getEnergy())
        {
            double distToWayPoint = determineHeadingChangeToGoToPoint(currentWayPoint, false);
            if (Math.abs(distToWayPoint) < 45.0)
            {
                currentWayPoint = getNextWayPoint();
                determineHeadingChangeToGoToPoint(currentWayPoint);
            }
            else
            {
//                robot.setAhead(45.0 * Utils.sign(distToWayPoint));
            }
        }*/
        
        if (robot.getOthers() > 1)
        {
            String enemyName = event.getName();
            Enemy closestEnemy = EnemyManager.getInstance().getClosestEnemy();
            if (enemyName == null || closestEnemy == null || !enemyName.equals(closestEnemy.getName()))
            {
                return;
            }
            
        }
        this.logger.log(Level.FINER, "\n");
        lastKnownEnergy = event.getEnergy();

        double turnAngleDegrees = event.getBearing() + 90.0 -
                (30.0 * FORWARD *(event.getDistance()>200.0?1.0:-1.0));

this.logger.log(Level.FINER, "robot.getDistanceRemaining(): " + robot.getDistanceRemaining());            
        if (robot.getDistanceRemaining() == 0.0)
        {
            FORWARD =-FORWARD;
            double forwardValue = 200 * Math.random() * FORWARD;
            
            this.nextLocation = determineNextLocation(robot.getHeading() + turnAngleDegrees, forwardValue);
            if(this.northBufferZone.contains(nextLocation))
            {
                System.out.println("north");
                forwardValue *= -1;
            }
            else if(this.eastBufferZone.contains(nextLocation))
            {
                System.out.println("east");
                forwardValue *= -1;
            }
            else if(this.southBufferZone.contains(nextLocation))
            {
                System.out.println("south");
                forwardValue *= -1;
            }
            else if(this.westBufferZone.contains(nextLocation))
            {
                System.out.println("west");
                forwardValue *= -1;
            }
            this.logger.log(Level.FINER, "forward value: " + forwardValue);
            robot.setAhead(forwardValue);
        }
        this.logger.log(Level.FINER, "\tturnAngleDegrees: " + turnAngleDegrees);
this.nextLocation = determineNextLocation(robot.getHeading() + turnAngleDegrees, robot.getDistanceRemaining());
        robot.setTurnRight(turnAngleDegrees);
       
    }
    
    private Point2D determineNextLocation(double robotsNewHeading, double robotsTravelDist)
    {
        double x = robot.getX() + (robotsTravelDist * Math.sin(Math.toRadians(robotsNewHeading)));
        double y = robot.getY() + (robotsTravelDist * Math.cos(Math.toRadians(robotsNewHeading)));
        return new Point2D.Double(x,y);
    }
    
    public void processEvent(HitByBulletEvent event)
    {
    }

    public void processEvent(HitRobotEvent event)
    {
        Enemy enemy = EnemyManager.getInstance().getEnemy(event.getName());
        // my enemy is in front of me set goBackward flag to true
        boolean goBackward = false;
        double turnRightAmount = 45.0;
        if (event.getBearing() > -90.0 && event.getBearing() < 90.0) 
        {
            // got to make sure I'm traveling in a forward direction
            // as I go backwards often
            goBackward = (robot.getDistanceRemaining() >= 0.0);
            if (goBackward)
            {
                turnRightAmount = -45.0;
            }
        }
        robot.setAhead((goBackward ? -200 : 200));
        if (robot.getTurnRemaining() == 0.0)
        {
            robot.setTurnRight(turnRightAmount);
        }
        // reverse the waypoint dirction
        NORMAL_DIRECTION = !NORMAL_DIRECTION;
    }
    
    public void processEvent(HitWallEvent event)
    {
        System.out.println("hit wall dr: " + robot.getDistanceRemaining());
        if (robot.getDistanceRemaining() == 0)
        {
            FORWARD =-FORWARD;
            double forwardValue = 185 * Math.random() * FORWARD;
            robot.setAhead(forwardValue);
        }
        /*
        // based upon me current heading determine a direction to go
        determineHeadingChangeToGoToPoint(new Point2D.Double(WIDTH/2.0, HEIGHT/2.0));
         */
    }
    public void processEvent(DeathEvent event)
    {
        reset();
    }
    public void processEvent(WinEvent event)
    {
        reset();

    }
    
    private void reset()
    {
        startGameProceduresExecuted = false;
        currentWayPoint = null;
        NORMAL_DIRECTION = true;
        robot.removeCustomEvent(moveCompleteCondition);
    }
    
    public void onPaint(java.awt.Graphics2D g)
    {
        /*
        g.setColor(java.awt.Color.RED);
        g.fill(northBufferZone);
        g.setColor(java.awt.Color.WHITE);
        g.fill(eastBufferZone);
        g.setColor(java.awt.Color.BLUE);
        g.fill(southBufferZone);
         *
        g.setColor(java.awt.Color.PINK);
        g.fill(westBufferZone);
*/
            g.setColor(java.awt.Color.RED);
            double CIRCLE_SIZE = 200.0;
            g.fill(new Rectangle2D.Double(nextLocation.getX(), nextLocation.getY(), 25.0, 25.0));
            //g.fill(new java.awt.geom.Ellipse2D.Double(nextLocation.getX() + (CIRCLE_SIZE/2.0), nextLocation.getY() + (CIRCLE_SIZE/2.0), CIRCLE_SIZE, CIRCLE_SIZE));

        /*
        g.setColor(java.awt.Color.RED);
        double CIRCLE_SIZE = 25.0;
        java.awt.Shape circle = null;
        for (Point2D pt : WAYPOINTLIST)
        {
            g.fill(new java.awt.geom.Ellipse2D.Double(pt.getX() + (CIRCLE_SIZE/2.0), pt.getY() + (CIRCLE_SIZE/2.0), CIRCLE_SIZE, CIRCLE_SIZE));
        }
        */
    }
    public static void main(String[] args)
    {
        int y = 0;
        Random randomVelocity = new Random();
        
        for (int i = 0; i < 100; i++)
        {
            System.out.println("randomVelocity.nextDouble(): " + (3.0*randomVelocity.nextDouble()));
        }
        
    }
}
